{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在第 3 章中，我们学习了几种方法，用于保护线程间的共享数据。然而，有时候我们不仅需要保护共享数据，还需要令独立线程上的行为同步。例如，某线程只有先等另一线程的任务完成，才可以执行自己的任务。一般而言，线程常常需要等待特定事件的发生，或等待某个条件成立。只要设置一个“任务完成”的标志，或者利用共享数据存储一个类似的标志，通过定期查验该标志就可以满足需求，但这远非理想方法。\n",
    "\n",
    "上述线程间的同步操作很常见，C++标准库专门为之提供了处理工具：条件变量（conditional variable）和 future。C++标准委员会发布的并发技术规约对这些工具做了扩展，增加了更多针对future 的操作，还提供了新式的同步工具：线程闩（latch）和线程卡（barrier）。\n",
    "\n",
    "本章我们会讨论如何使用条件变量、线程闩、线程卡和 future 以等待事件，并且简化同步操作。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 4.1 等待事件或等待其他条件"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "设想你坐夜行列车外出。如果要保证在正确的站点下车，一种方法是彻夜不眠，留心列车停靠的站点，那样就不会错过。可是，到达时你可能会精神疲倦。或者，你可以查看时刻表，按预定到达时间提前设定闹钟，随后安心入睡。这种方法还算管用，一般来说，你不会误站。但若列车晚点，你反而会太早醒来；也可能不巧，闹钟的电池刚好耗尽，结果你睡过头而错过下车站点。最理想的方法是，安排人员或设备，无论列车在什么时刻抵达目的站点，都可以将你唤起，那么你大可“高枕无忧”。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这与多线程有关联吗？确实有。如果线程甲需要等待线程乙完成任务(1)，可以采取几种不同方式。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> (1) 请读者特别注意甲、乙线程之间的先后和主次关系，后文将多次提及。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**方式一**：在共享数据内部维护一标志（受互斥保护），线程乙完成任务后，就设置标志成立。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "该方式存在双重浪费：线程甲须不断查验标志，浪费原本有用的处理时间；另外，一旦互斥被锁住，则其他任何线程无法再加锁。\n",
    "\n",
    "这两点都是线程甲的弊病：如果它正在运行，就会限制线程乙可用的算力；还有，线程甲每次查验标志，都要锁住互斥以施加保护，那么，若线程乙恰好同时完成任务，也意欲设置标志成立，则无法对互斥加锁。这就像是你整晚熬夜，不停地与列车司机攀谈，于是他不得不放慢车速，因为你老使他走神，结果列车晚点。类似地，线程甲白白耗费了计算资源，它们本来可用于系统中的其他线程，导致最终毫无必要地延长了等待时间。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**方式二**：让线程甲调用 `std::this_thread::sleep_for()`函数（详见 4.3 节），在各次查验之间短期休眠。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "input_line_12:4:22: error: function definition is not allowed here\n",
      "void wait_for_flag() {\n",
      "                     ^\n"
     ]
    },
    {
     "ename": "Interpreter Error",
     "evalue": "",
     "output_type": "error",
     "traceback": [
      "Interpreter Error: "
     ]
    }
   ],
   "source": [
    "bool flag;\n",
    "std::mutex m;\n",
    "void wait_for_flag() {\n",
    "    std::unique_lock<std::mutex> lk(m);\n",
    "    while (!flag) {\n",
    "        lk.unlock(); // ① 解锁互斥\n",
    "        std::this_thread::sleep_for(std::chrono::milliseconds(100)); // ② 休眠100毫秒\n",
    "        lk.lock(); // ③ 重新锁住互斥\n",
    "    }\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "上面的代码在每轮循环中，先将互斥解锁①，随之休眠②，再重新加锁③，从而别的线程有机会获取锁，得以设置标志成立。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这确有改进，因为线程休眠，所以处理时间不再被浪费。然而，休眠期的长短却难以预知。休眠期太短，线程仍会频繁查验，虚耗处理时间；休眠期太长，则令线程过度休眠。如果线程乙完成了任务，线程甲却没有被及时唤醒，就会导致延迟。过度休眠很少直接影响普通程序的运作。但是，对于高速视频游戏，过度休眠可能会造成丢帧；对于实时应用，可能会使某些时间片计算超时。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**方式三**：使用 C++标准库的工具等待事件发生。我们优先采用这种方式。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "以上述甲、乙两线程的二级流水线模式为例，若数据要先进行前期处理，才可以开始正式操作，那么线程甲则需等待线程乙完成并且触发事件，其中最基本的方式是**条件变量**。\n",
    "\n",
    "按照“条件变量”的概念，若条件变量与某一事件或某一条件关联，一个或多个线程就能以其为依托，等待条件成立。当某线程判定条件成立时，就通过该条件变量，知会所有等待的线程，唤醒它们继续处理。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 凭借条件变量等待条件成立 "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "C++标准库提供了条件变量的两种实现：`std::condition_variable` 和 `std::condition_variable_any`。它们都在标准库的头文件`<condition_variable>`内声明。两者都需配合互斥，方能提供妥当的同步操作。\n",
    "\n",
    "`std::condition_variable` 仅限于与 `std::mutex` 一起使用；然而，只要某一类型符合成为互斥的最低标准，足以充当互斥，`std::condition_variable_any` 即可与之配合使用，因此它的后缀是“_any”。由于 `std::condition_variable_any` 更加通用，它可能产生额外开销，涉及其性能、自身的体积或系统资源等，因此 `std::condition_variable` 应予优先采用，除非有必要令程序更灵活。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "那么，要解决前面介绍的问题，我们应该如何利用 `std::condition_variable`？为了让线程甲休眠，直至有数据需处理才被唤醒，我们要怎么做？代码清单 4.1 运用条件变量，展示了一种可行的方式。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "input_line_9:4:27: error: function definition is not allowed here\n",
      "data_chunk prepare_data() { return data_chunk(); }\n",
      "                          ^\n",
      "input_line_9:5:27: error: function definition is not allowed here\n",
      "void process(data_chunk&) {}\n",
      "                          ^\n",
      "input_line_9:6:33: error: function definition is not allowed here\n",
      "bool is_last_chunk(data_chunk&) { return true; }\n",
      "                                ^\n",
      "input_line_9:10:32: error: function definition is not allowed here\n",
      "void data_preparation_thread() { // 由线程乙运行\n",
      "                               ^\n",
      "input_line_9:20:31: error: function definition is not allowed here\n",
      "void data_processing_thread() { // 由线程甲运行\n",
      "                              ^\n",
      "input_line_9:31:12: error: function definition is not allowed here\n",
      "int main() {\n",
      "           ^\n"
     ]
    },
    {
     "ename": "Interpreter Error",
     "evalue": "",
     "output_type": "error",
     "traceback": [
      "Interpreter Error: "
     ]
    }
   ],
   "source": [
    "// 代码清单 4.1 用 std::condition_variable 等待处理数据\n",
    "\n",
    "#include <condition_variable>\n",
    "#include <mutex>\n",
    "#include <queue>\n",
    "#include <thread>\n",
    "\n",
    "bool more_data_to_prepare() { return false; }\n",
    "\n",
    "struct data_chunk {};\n",
    "\n",
    "data_chunk prepare_data() { return data_chunk(); }\n",
    "\n",
    "void process(data_chunk&) {}\n",
    "\n",
    "bool is_last_chunk(data_chunk&) { return true; }\n",
    "\n",
    "std::mutex mut;\n",
    "std::queue<data_chunk> data_queue; // 1\n",
    "std::condition_variable data_cond;\n",
    "\n",
    "void data_preparation_thread() { // 由线程乙运行\n",
    "    while (more_data_to_prepare()) {\n",
    "        data_chunk const data = prepare_data();\n",
    "        {\n",
    "          std::lock_guard<std::mutex> lk(mut);\n",
    "          data_queue.push(data); // 2\n",
    "        }\n",
    "        data_cond.notify_one(); // 3\n",
    "    }\n",
    "}\n",
    "\n",
    "void data_processing_thread() { // 由线程甲运行\n",
    "    while (true) {\n",
    "        std::unique_lock<std::mutex> lk(mut); // 4\n",
    "        data_cond.wait(lk, [] { return !data_queue.empty(); }); // 5\n",
    "        data_chunk data = data_queue.front();\n",
    "        data_queue.pop();\n",
    "        lk.unlock(); // 6\n",
    "        process(data);\n",
    "        if (is_last_chunk(data)) break;\n",
    "    }\n",
    "}\n",
    "\n",
    "int main() {\n",
    "    std::thread t1(data_preparation_thread);\n",
    "    std::thread t2(data_processing_thread);\n",
    "\n",
    "    t1.join();\n",
    "    t2.join();\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "首先，我们使用 `std::queue` 队列在两个线程之间传递数据①。一旦线程乙准备好数据，就使用 `std::lock_guard` 锁住互斥以保护队列，并压入数据②。然后，线程乙调用`std::condition_variable` 实例的成员函数 `notify_one()`，通知线程甲③（如果它确实正等待着）。请注意，我们特地使用一个较小的代码块(1)，放置压入数据的代码，目的是在解锁互斥后通知条件变量。若线程甲立刻觉醒，也无须等待互斥解锁，从而不会被阻塞(2)。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> (1) 即②③处的内层花括号。这里运用了 C++的 RAII 过程的处理手法，一旦执行流程离开该代码块，锁 `lk` 会自行销毁，从而自动解锁。<br/>\n",
    "> (2) 假设内层花括号不存在，则锁 `lk` 的生存期持续至 while 循环末尾。若线程甲在语句③执行后立即觉醒，而互斥仍被锁住，则线程甲还需等待锁 `lk` 销毁。内层花括号令锁 `lk` 在语句③执行前销毁，即在线程甲觉醒前解锁互斥，算是微小的性能改进。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "同时，线程甲等待接收处理数据。这次，它先对互斥加锁，但使用的是`std::unique_lock`而非 `std::lock_guard`④（我们很快会明白缘由）。线程甲在 `std::condition_variable` 实例上调用 `wait()`，传入锁对象和一个 lambda 函数，后者用于表达需要等待成立的条件⑤。\n",
    "\n",
    "lambda 函数是 C++11 的新特性，它准许我们编写匿名函数，将其作为另一个表达式的组件。此外，某些标准库函数（如 wait()）需要我们指定断言，而 lambda 函数简直是为此量身订做的工具。\n",
    "本例中，`[]{return !data_queue.empty();}`是一个简单的 lambda 函数，它检查容器 `data_queue` 是否为空。若否，则说明已有数据备妥，存放在队列中等待处理。附录 A.5 节将对 lambda 函数进行更详细的介绍。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "接着，`wait()`在内部调用传入的 lambda 函数，判断条件是否成立：若成立（lambda函数返回 `true`），则 `wait()`返回；否则（lambda 函数返回 `false`），`wait()`解锁互斥，并令线程进入阻塞状态或等待状态。线程乙将数据准备好后，即调用 `notify_one()`通知条件变量，线程甲随之从休眠中觉醒（阻塞解除），重新在互斥上获取锁，再次查验条件：若条件成立，则从 `wait()`函数返回，而互斥仍被锁住；若条件不成立，则线程甲解锁互斥，并继续等待。我们舍弃 `std::lock_guard` 而采用 `std::unique_lock`，原因就在这里：线程甲在等待期间，必须解锁互斥，而结束等待之后，必须重新加锁，但 `std::lock_guard` 无法提供这种灵活性。假设线程甲在休眠的时候，互斥依然被锁住，那么即使线程乙备妥了数据，也不能锁住互斥，无法将其添加到队列中。结果线程甲所等待的条件永远不能成立，它将无止境地等下去。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "代码清单 4.1 中，等待终止的条件判定需要查验队列是否非空⑤，为此，我们使用了简单的 lambda 函数。其实，也可以向 `wait()`传递普通函数或可调用对象。\n",
    "\n",
    "本例只进行简单判定，实际上条件判定的函数有可能更加复杂，因而我们需事先另行写出。那么，该判定函数就可以被直接传入，无须用 lambda 表达式包装。在 `wait()`的调用期间，条件变量可以多次查验给定的条件，次数不受限制；在查验时，互斥总会被锁住；另外，当且仅当传入的判定函数返回 `true` 时（它判定条件成立），`wait()`才会立即返回。如果线程甲重新获得互斥，并且查验条件，而这一行为却不是直接响应线程乙的通知，则称之为伪唤醒（spurious wake）。按照 C++标准的规定，这种伪唤醒出现的数量和频率都不确定。故此，若判定函数有副作用(1)，则不建议选取它来查验条件。倘若读者真的要这么做，就有可能多次产生副作用，所以必须准备好应对方法。譬如，每次被调用时，判定函数就顺带提高所属线程的优先级，该提升动作即产生的副作用。结果，多次伪唤醒可“意外地”令线程优先级变得非常高。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> (1) 单纯的判定函数本应仅仅查验条件成立与否，而不会连带执行其他任何操作。但出于种种原因，某些代码并不遵从此设计原则。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 利用条件变量构建线程安全的队列 "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "假使读者打算构建通用的队列容器，就值得花点儿时间考虑清楚哪些操作最有必要，这与 3.2.3 节中，我们反复推敲线程安全的栈容器一样。C++标准程序库提供了`std::queue<>`容器适配器，如代码清单 4.2 所示，我们看看它将为我们带来什么启发。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// 代码清单 4.2 std::queue 接口\n",
    "\n",
    "#include <cstddef>\n",
    "#include <deque>\n",
    "\n",
    "template <class T, class Container = std::deque<T> >\n",
    "class queue {\n",
    "   public:\n",
    "    explicit queue(const Container&);\n",
    "    explicit queue(Container&& = Container());\n",
    "    queue(queue&& q);\n",
    "\n",
    "    template <class Alloc>\n",
    "    explicit queue(const Alloc&);\n",
    "    template <class Alloc>\n",
    "    queue(const Container&, const Alloc&);\n",
    "    template <class Alloc>\n",
    "    queue(Container&&, const Alloc&);\n",
    "    template <class Alloc>\n",
    "    queue(queue&&, const Alloc&);\n",
    "\n",
    "    queue& operator=(queue&& q);\n",
    "    void swap(queue&& q);\n",
    "\n",
    "    bool empty() const;\n",
    "    typedef typename std::deque<T>::size_type size_type;\n",
    "    size_type size() const;\n",
    "\n",
    "    T& front();\n",
    "    const T& front() const;\n",
    "    T& back();\n",
    "    const T& back() const;\n",
    "\n",
    "    void push(const T& x);\n",
    "    void push(T&& x);\n",
    "    void pop();\n",
    "};\n",
    "\n",
    "int main() {}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "假如我们忽略构造、赋值和互换，那么只剩下 3 组操作：\n",
    "- 一是 `empty()`和 `size()`，用于查询队列的整体状态；\n",
    "- 二是 `front()`和 `back()`，用于查询队列中的元素；\n",
    "- 三是 `push()`、`pop()`和 `emplace()`，用于修改队列。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这与 3.2.3 节的栈容器相似，其接口还是存在固有的条件竞争。所以，我们需要把`front()`和 `pop()`合并成一个函数，这与栈容器的 `top()`和 `pop()`的合并十分相似。\n",
    "\n",
    "代码清单4.1 还新增了一点儿细节：当队列用于线程间的数据传递时，负责接收的线程常常需要等待数据压入。我们要对外提供 `pop()`的两个变体，`try_pop()`和`wait_and_pop()`：它们都试图弹出队首元素，前者总是立即返回，即便队列内空无一物（通过返回值示意操作失败）；后者会一直等到有数据压入可供获取。依照前文的栈容器范例，我们借鉴其成员函数签名，则队列容器的接口看起来就像代码清单 4.3 所示的内容。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// 代码清单 4.3 线程安全的队列容器 threadsafe_queue 的接口\n",
    "\n",
    "#include <memory> // 为使用std::shared_ptr而包含此头文件\n",
    "\n",
    "template <typename T>\n",
    "class threadsafe_queue {\n",
    "   public:\n",
    "    threadsafe_queue();\n",
    "    threadsafe_queue(const threadsafe_queue&);\n",
    "    threadsafe_queue& operator=(const threadsafe_queue&) = delete; // 为简化设计而禁止赋值操作\n",
    "\n",
    "    void push(T new_value);\n",
    "\n",
    "    bool try_pop(T& value); // 3\n",
    "    std::shared_ptr<T> try_pop(); // 4\n",
    "\n",
    "    void wait_and_pop(T& value);\n",
    "    std::shared_ptr<T> wait_and_pop();\n",
    "\n",
    "    bool empty() const;\n",
    "};\n",
    "\n",
    "int main() {}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "为了简化代码，我们删除了构造函数和赋值操作符，处理手法和栈容器相似。\n",
    "\n",
    "与代码清单 4.2 一样，针对 `try_pop()`和 `wait_for_pop()`，我们都给出两个版本。在 `try_pop()`的第一个重载中③，由参数所引用的变量保存获取的值，则函数能通过返回值指明操作成功与否：若成功获取值就返回 `true`，否则返回 `false`（见附录 A.2 节）。函数的第二个重载④却无法做到这点，因为它直接返回获取的值。只不过，如果它没有获取值，则返回的指针会被设置成 `NULL`。\n",
    "\n",
    "然而，以上这些与代码清单 4.1 有什么关联呢？其实，只要依据代码清单 4.1，我们就可以写出`push()`和 `wait_and_pop()`，如代码清单 4.4 所示。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// 代码清单 4.4 由代码清单 4.1 衍化出 push()和 wait_and_pop()\n",
    "\n",
    "#include <condition_variable>\n",
    "#include <mutex>\n",
    "#include <queue>\n",
    "\n",
    "template <typename T>\n",
    "class threadsafe_queue {\n",
    "   private:\n",
    "    std::mutex mut;\n",
    "    std::queue<T> data_queue;\n",
    "    std::condition_variable data_cond;\n",
    "\n",
    "   public:\n",
    "    void push(T new_value) {\n",
    "        std::lock_guard<std::mutex> lk(mut);\n",
    "        data_queue.push(new_value);\n",
    "        data_cond.notify_one();\n",
    "    }\n",
    "\n",
    "    void wait_and_pop(T& value) {\n",
    "        std::unique_lock<std::mutex> lk(mut);\n",
    "        data_cond.wait(lk, [this] { return !data_queue.empty(); });\n",
    "        value = data_queue.front();\n",
    "        data_queue.pop();\n",
    "    }\n",
    "};\n",
    "\n",
    "struct data_chunk {};\n",
    "\n",
    "data_chunk prepare_data();\n",
    "bool more_data_to_prepare();\n",
    "void process(data_chunk);\n",
    "bool is_last_chunk(data_chunk);\n",
    "\n",
    "threadsafe_queue<data_chunk> data_queue; // 1\n",
    "\n",
    "void data_preparation_thread() {\n",
    "    while (more_data_to_prepare()) {\n",
    "        data_chunk const data = prepare_data();\n",
    "        data_queue.push(data); // 2\n",
    "    }\n",
    "}\n",
    "\n",
    "void data_processing_thread() {\n",
    "    while (true) {\n",
    "        data_chunk data;\n",
    "        data_queue.wait_and_pop(data); // 3\n",
    "        process(data);\n",
    "        if (is_last_chunk(data)) break;\n",
    "    }\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这样互斥和条件变量就都包含在 `threadsafe_queue` 实例中了，而不必使用单独的变量①，`push()`的调用也无须与外部同步②。`wait_and_pop()`还负责处理条件变量上的等待③。我们不太费力就能写出 `wait_and_pop()`的另一个重载，而其余成员函数的代码，几乎原样复制代码清单 3.5 的栈容器范例即可。代码清单 4.5 是队列容器的最终实现。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// 代码清单 4.5 线程安全队列的完整的类定义，其中采用了条件变量\n",
    "\n",
    "#include <condition_variable>\n",
    "#include <memory>\n",
    "#include <mutex>\n",
    "#include <queue>\n",
    "\n",
    "template <typename T>\n",
    "class threadsafe_queue {\n",
    "   private:\n",
    "    mutable std::mutex mut; // 1. 互斥必须用 mutable 修饰（针对 const 对象，准许其数据成员发生变动\n",
    "    std::queue<T> data_queue;\n",
    "    std::condition_variable data_cond;\n",
    "\n",
    "   public:\n",
    "    threadsafe_queue() {}\n",
    "    threadsafe_queue(threadsafe_queue const& other) {\n",
    "        std::lock_guard<std::mutex> lk(other.mut);\n",
    "        data_queue = other.data_queue;\n",
    "    }\n",
    "\n",
    "    void push(T new_value) {\n",
    "        std::lock_guard<std::mutex> lk(mut);\n",
    "        data_queue.push(new_value);\n",
    "        data_cond.notify_one();\n",
    "    }\n",
    "\n",
    "    void wait_and_pop(T& value) {\n",
    "        std::unique_lock<std::mutex> lk(mut);\n",
    "        data_cond.wait(lk, [this] { return !data_queue.empty(); });\n",
    "        value = data_queue.front();\n",
    "        data_queue.pop();\n",
    "    }\n",
    "\n",
    "    std::shared_ptr<T> wait_and_pop() {\n",
    "        std::unique_lock<std::mutex> lk(mut);\n",
    "        data_cond.wait(lk, [this] { return !data_queue.empty(); });\n",
    "        std::shared_ptr<T> res(std::make_shared<T>(data_queue.front()));\n",
    "        data_queue.pop();\n",
    "        return res;\n",
    "    }\n",
    "\n",
    "    bool try_pop(T& value) {\n",
    "        std::lock_guard<std::mutex> lk(mut);\n",
    "        if (data_queue.empty()) return false;\n",
    "        value = data_queue.front();\n",
    "        data_queue.pop();\n",
    "        return true;\n",
    "    }\n",
    "\n",
    "    std::shared_ptr<T> try_pop() {\n",
    "        std::lock_guard<std::mutex> lk(mut);\n",
    "        if (data_queue.empty()) return std::shared_ptr<T>();\n",
    "        std::shared_ptr<T> res(std::make_shared<T>(data_queue.front()));\n",
    "        data_queue.pop();\n",
    "        return res;\n",
    "    }\n",
    "\n",
    "    bool empty() const {\n",
    "        std::lock_guard<std::mutex> lk(mut);\n",
    "        return data_queue.empty();\n",
    "    }\n",
    "};"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "虽然 `empty()`是 `const` 成员函数，拷贝构造函数的形参 `other` 也是 `const` 引用，但是其他线程有可能以非 `const` 形式引用队列容器对象，也可能调用某些成员函数，它们会改动数据成员，因此我们仍需锁定互斥。由于互斥因锁操作而变化，因此它必须用关键字`mutable` 修饰①，这样才可以在 `empty()`函数和拷贝构造函数中锁定。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "条件变量也适用于多个线程都在等待同一个目标事件的情况。若要将工作负荷分配给多个线程，那么，对于每个通知应该仅有一个线程响应。\n",
    "\n",
    "这种代码结构与代码清单 4.1所示的完全一致，可以参考利用，唯一的改动是需要借助多个 `std::thread` 实例，运行多个数据处理的线程。如果多个线程因执行 `wait()`而同时等待，每当有新数据就绪并加入`data_queue`（见成员函数 `push()`）时，`notify_one()`的调用就会触发其中一个线程去查验条件，让它从 `wait()`返回。此方式并不能确定会通知到具体哪个线程，甚至不能保证正好有线程在等待通知，因为可能不巧，负责数据处理的全部线程都尚未完工。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如果几个线程都在等待同一个目标事件，那么还存在另一种可能的行为方式：它们全部需要做出响应。\n",
    "\n",
    "以上行为会在两种情形下发生：\n",
    "- 一是共享数据的初始化，所有负责处理的线程都用到同一份数据，但都需要等待数据初始化完成；\n",
    "- 二是所有线程都要等待共享数据更新（如定期执行的重新初始化）。\n",
    "\n",
    "尽管条件变量适用于第一种情形，但我们可以选择其他更好的处理方式，如 `std::call_once()`函数，详细讨论见 3.3.1 节。前文中，负责准备的线程原本在条件变量上调用 `notify_one()`，而这里只需改为调用成员函数`notify_all()`。顾名思义，该函数通知当前所有执行 `wait()`而正在等待的线程，让它们去查验所等待的条件。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "假定，某个线程按计划仅仅等待一次，只要条件成立一次，它就不再理会条件变量。条件变量未必是这种同步模式的最佳选择。若我们所等待的条件需要判定某份数据是否可用，上述论断就非常正确。`future` 更适合此场景。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 4.2 使用 future 等待一次性事件发生"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "设想你要坐飞机去海外度假。即便你到达机场并办妥了各种登机手续，也要等待登机通知，而且可能要等好几个小时。你可能自有“良方”打发时间，例如读书、上网，或在机场茶座就餐，但本质上你只不过是在等一句广播：登机时间已到。此外，无论乘坐哪趟航班，该趟航班都仅会起飞一次；下次再出游，你就要等待别的航班。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "C++标准程序库使用 `future` 来模拟这类一次性事件：若线程需等待某个特定的一次性事件发生，则会以恰当的方式取得一个 `future`，它代表目标事件；接着，该线程就能一边执行其他任务（光顾机场茶座），一边在 `future` 上等待；同时，它以短暂的间隔反复查验目标事件是否已经发生（查看出发时刻表）。这个线程也可以转换运行模式，先不等目标事件发生，直接暂缓当前任务，而切换到别的任务，及至必要时，才回头等待`future` 准备就绪。`future` 可能与数据关联（如航班的登机口），也可能未关联。一旦目标事件发生，其 `future` 即进入就绪状态，无法重置。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "C++标准程序库有两种 `future`，分别由两个类模板实现，其声明位于标准库的头文件`<future>`内：\n",
    "- 独占 `future`（`unique future`，即 `std::future<>`）\n",
    "- 和共享 `future`（`shared future`，即 `std::shared_future<>`）。\n",
    "\n",
    "\n",
    "它们的设计参照了 `std::unique_ptr` 和 `std::shared_ptr`。同一事件仅仅允许关联唯一一个 `std::future` 实例，但可以关联多个 `std::shared_future` 实例。只要目标事件发生，与后者关联的所有实例就会同时就绪，并且，它们全都可以访问与该目标事件关联的任何数据。关联数据正是两种 `future` 以模板形式实现的原因；模板参数就是关联数据的类型，这与 `std::unique_ptr` 和 `std::shared_ptr` 相似。如果没有关联数据，我们应使用特化的模板 `std::future<void>`和 `std::shared_future<void>`。\n",
    "\n",
    "虽然 `future` 能用于线程间通信，但是 `future` 对象本身不提供同步访问。若多个线程需访问同一个 `future` 对象，必须用互斥或其他同步方式进行保护，如第 3 章所述。不过，4.2.5 节将说明，一个`std::shared_future<>`对象可能派生出多个副本，这些副本都指向同一个异步结果，由多个线程分别独占，它们可访问属于自己的那个副本而无须互相同步。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "C++的并发技术规约在 `std::experimental` 名字空间中给出了上述类模板的扩展版本：`std::experimental::future<>`和 `std::experimental::shared_future<>`。\n",
    "\n",
    "它们的行为与 `std` 名字空间中的版本一致，但具备额外的成员函数，可提供更多功能。必须指出，名字空间“`std::experimental`”的名字（experimental）意在强调其中所含的类和函数尚未被 C++标准正式采纳，而非表示代码质量优劣（我也希望，你的程序库厂商对该名字空间所做的实现质量可靠，不亚于他们交付的其他代码）。故此，若今后它们最终正式进入 C++标准，其语法和语义可能不会与现在完全相同。要使用这些工具，我们必须包含头文件`<experimental/future>`。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "最基本的一次性事件是，置于后台运行的计算任务完成，得出结果。回顾第 2 章，我们当时已发现，`std::thread` 类型并未提供简洁的方式从计算任务返回求得的值，我们承诺了问题将在第 4 章利用 `future` 解决，现在是时候来看看具体做法了。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 从后台任务返回值 "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在作家 Douglas Adams 笔下，有一事例简直是为多线程量体裁衣，我们在此借用：假定读者发现了绝妙的求解方法，能得出生命、宇宙和一切问题的终极答案(1)，虽然运算过程异常耗时，可是你确定终究会得到有用的结果，并且我们还假定你暂不需要这个结果。那么，你大可顺势而为，启动新线程进行运算。但因为 `std::thread` 没有提供直接回传结果的方法，而你不得不为此亲自手动编码解决，所以函数模板 `std::async()`应运而生（其声明也位于头文件`<future>`中）。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "只要我们并不急需线程运算的值，就可以使用 `std::async()`按异步方式启动任务。我们从 `std::async()`函数处获得 `std::future` 对象（而非 `std::thread` 对象），运行的函数一旦完成，其返回值就由该对象最后持有。若要用到这个值，只需在 `future` 对象上调用 `get()`，当前线程就会阻塞，以便 `future` 准备妥当并返回该值。代码清单 4.6 展示了 `future` 的简例。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// 代码清单 4.6 运用 std::future 取得异步任务的函数返回值\n",
    "\n",
    "#include <future>\n",
    "#include <iostream>\n",
    "\n",
    "int find_the_answer_to_ltuae() { return 42; }\n",
    "\n",
    "void do_other_stuff() {}\n",
    "\n",
    "int main() {\n",
    "  std::future<int> the_answer = std::async(find_the_answer_to_ltuae);\n",
    "  do_other_stuff();\n",
    "  std::cout << \"The answer is \" << the_answer.get() << std::endl;\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在调用 `std::async()`时，它可以接收附加参数，进而传递给任务函数作为其参数，此方式与 `std::thread `的构造函数相同。\n",
    "\n",
    "若要异步运行某个类的某成员函数，则 `std::async()`的第一个参数应是一个函数指针，指向该类的目标成员函数；第二个参数需要给出相应的对象，以在它之上调用成员函数（这个参数可以是指向对象的指针，或对象本身，或由 `std::ref` 包装的对象）；余下的 `std::async()`的参数会传递给成员函数，用作成员函数的参数。除此以外，异步运行的就是普通函数，所以 `std::async()`根据第一个参数指定任务函数（或目标可调用对象），其参数取自 `std::async()`余下的参数。如果 `std::async()`的参数是右值，则通过移动原始参数构建副本，与复制 `std::thread` 实例相同。这使得仅可移动的类型（move-only type）(1)既能作为函数对象，又能充当 `std::async()`的参数。\n",
    "\n",
    "请参见代码清单 4.7。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> (1) 指那些仅仅支持移动复制和移动赋值的类型，它们不支持拷贝复制或拷贝赋值"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// 代码清单 4.7 通过 std::async()向任务函数传递参数 (2)\n",
    "\n",
    "#include <future>\n",
    "#include <string>\n",
    "#include <iostream>\n",
    "#include <functional>\n",
    "\n",
    "struct X {\n",
    "    void foo(int a, std::string const &str) {\n",
    "        std::cout << \"X::foo called with a = \" << a << \", str = \" << str << std::endl;\n",
    "    }\n",
    "    std::string bar(std::string const &str) {\n",
    "        std::cout << \"X::bar called with str = \" << str << std::endl;\n",
    "        return \"Result from bar: \" + str;\n",
    "    }\n",
    "};\n",
    "\n",
    "\n",
    "X x;\n",
    "\n",
    "auto f1 = std::async(&X::foo, &x, 42, \"hello\"); // 1. 调用 p->foo(42,\"hello\") ，其中 p 的值是&x，即 x 的地址\n",
    "\n",
    "auto f2 = std::async(&X::bar, &x, \"goodbye\"); // 2. 调用 tmpx.bar(\"goodbye\")，其中 tmpx 是 x 的副本"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// 定义结构体 Y\n",
    "struct Y {\n",
    "    double operator()(double d) {\n",
    "        std::cout << \"Y::operator() called with d = \" << d << std::endl;\n",
    "        return d * 2;\n",
    "    }\n",
    "};\n",
    "\n",
    "Y y;\n",
    "auto f3 = std::async(Y(), 3.141); // 3. 调用 tmpy(3.141)。其中，由 Y()生成一个匿名变量，传递给 std::async()，\n",
    "                                  // 进而发生移动构造。在std::async()内部产生对象 tmpy，在 tmpy 上执行Y::operator()(3.141) \n",
    "auto f4 = std::async(std::ref(y), 2.718); // 4. 调用 y(2.718) "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// 定义函数 baz\n",
    "X baz(X &x) {\n",
    "    std::cout << \"baz called\" << std::endl;\n",
    "    return x;\n",
    "}\n",
    "\n",
    "auto f6 = std::async(baz, std::ref(x)); // 5. 调用 baz(x) "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// 定义类 move_only\n",
    "class move_only {\n",
    "   public:\n",
    "    move_only() {\n",
    "        std::cout << \"move_only constructed\" << std::endl;\n",
    "    }\n",
    "    move_only(move_only &&) {\n",
    "        std::cout << \"move_only move-constructed\" << std::endl;\n",
    "    }\n",
    "    move_only &operator=(move_only &&) {\n",
    "        std::cout << \"move_only move-assigned\" << std::endl;\n",
    "        return *this;\n",
    "    }\n",
    "    move_only(move_only const &) = delete;\n",
    "    move_only &operator=(move_only const &) = delete;\n",
    "    void operator()() {\n",
    "        std::cout << \"move_only::operator() called\" << std::endl;\n",
    "    }\n",
    "};\n",
    "\n",
    "auto f5 = std::async(move_only()); // 6. 调用 tmp()，其中 tmp 等价于 std::move (move_only())，它的产生过程与③相似"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "按默认情况下，`std::async()`的具体实现会自行决定—等待 `future` 时，是启动新线程，还是同步执行任务。大多数情况下，我们正希望如此。\n",
    "\n",
    "不过，我们还能够给 `std::async()`补充一个参数，以指定采用哪种运行方式。参数的类型是 `std::launch`，其值可以是`std::launch::deferred` 或 `std::launch::async`(1)。前者指定在当前线程上延后调用任务函数，等到在 `future` 上调用了 `wait()`或 `get()`，任务函数才会执行；后者指定必须另外开启专属的线程，在其上运行任务函数。该参数的值还可以是 `std::launch::deferred | std::launch::async`，表示由 `std::async()`的实现自行选择运行方式。最后这项是参数的默认值。若延后调用任务函数，则任务函数有可能永远不会运行。\n",
    "\n",
    "举例如下。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "auto f6 = std::async(std::launch::async, Y(), 1.2);  // ①运行新线程\n",
    "auto f7 = std::async(std::launch::deferred, baz, std::ref(x));  // ②在 wait() 或 get()内部运行任务函数\n",
    "auto f8 = std::async(std::launch::deferred | std::launch::async, baz, std::ref(x));  // ③交由实现自行选择运行方式\n",
    "auto f9 = std::async(baz, std::ref(x));\n",
    "f7.wait();  // ④前面②处的任务函数调用被延后，到这里才运行"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "本章的后半部分和第 8 章将向读者讲解，凭借 `std::async()`，即能简便地把算法拆分成多个子任务，且可并发运行。不过，使 `std::future` 和任务关联并非唯一的方法：运用类模板 `std::packaged_task<>`的实例，我们也能将任务包装起来；又或者，利用`std::promise<>`类模板编写代码，显式地异步求值。与 `std::promise` 相比，`std::packaged_task`的抽象层级更高，我们从它开始介绍。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 关联 future 实例和任务 "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`std::packaged_task<>`连结了 `future` 对象与函数（或可调用对象）。`std::packaged_task<>`对象在执行任务时，会调用关联的函数（或可调用对象），把返回值保存为 `future` 的内部数据，并令 `future` 准备就绪。它可作为线程池的构件单元（见第 9 章），亦可用于其他任务管理方案。\n",
    "\n",
    "例如，为各个任务分别创建专属的独立运行的线程，或者在某个特定的后台线程上依次执行全部任务。若一项庞杂的操作能分解为多个子任务，则可把它们分别包装到多个 `std::packaged_task<>`实例之中，再传递给任务调度器或线程池。这就隐藏了细节，使任务抽象化，让调度器得以专注处理 `std::packaged_task<>`实例，无须纠缠于形形色色的任务函数。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`std::packaged_task<>`是类模板，其模板参数是函数签名（function signature）：\n",
    "- 譬如，`void()`表示一个函数，不接收参数，也没有返回值；\n",
    "- 又如，`int(std::string&,double*)`代表某函数，它接收两个参数并返回 `int` 值，其中，第一个参数是非 `const` 引用，指向 `std::string`对象，第二个参数是 `double` 类型的指针。\n",
    "\n",
    "假设，我们要构建 `std::packaged_task<>`实例，那么，由于模板参数先行指定了函数签名，因此传入的函数（或可调用对象）必须与之相符，即它应接收指定类型的参数，返回值也必须可以转换为指定类型。这些类型不必严格匹配，若某函数接收 `int` 类型参数并返回 `float` 值，我们则可以为其构建 `std::packaged_ task<double(double)>`的实例，因为对应的类型可进行隐式转换。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "类模板 `std::packaged_task<>`具有成员函数 `get_future()`，它返回 `std::future<>`实例，该 `future` 的特化类型取决于函数签名所指定的返回值。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`std::packaged_task<>`还具备函数调用操作符，它的参数取决于函数签名的参数列表。\n",
    "\n",
    "代码清单 4.8 以 `std::packaged_task <std::string(std::vector<char>*,int)>`为例，展示特化的类定义。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// 代码清单 4.8 定义特化的 std::packaged_task<>类模板\n",
    "\n",
    "#include <future>\n",
    "#include <string>\n",
    "#include <vector>\n",
    "\n",
    "template <>\n",
    "class packaged_task<std::string(std::vector<char>*, int)> {\n",
    "   public:\n",
    "    template <typename Callable>\n",
    "    explicit packaged_task(Callable&& f);\n",
    "    std::future<std::string> get_future();\n",
    "    void operator()(std::vector<char>*, int);\n",
    "};"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`std::packaged_task` 对象是可调用对象，我们可以直接调用，还可以将其包装在std::function 对象内，当作线程函数传递给 `std::thread` 对象，也可以传递给需要可调用对象的函数。若 `std::packaged_task` 作为函数对象而被调用，它就会通过函数调用操作符接收参数，并将其进一步传递给包装在内的任务函数，由其异步运行得出结果，并将结果保存到 `std::future` 对象内部，再通过 `get_future()`获取此对象。因此，为了在未来的适当时刻执行某项任务，我们可以将其包装在 `std::packaged_task` 对象内，取得对应的 future之后，才把该对象传递给其他线程，由它触发任务执行。等到需要使用结果时，我们静候 `future` 准备就绪即可。下面的例子解释了这一行为过程。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**在线程间传递任务***"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "许多图形用户界面（Graphical User Interface，GUI）框架都设立了专门的线程，作为更新界面的实际执行者。若别的线程需要更新界面，就必须向它发送消息，由它执行操作。该模式可以运用 `std::packaged_task` 实现，如代码清单 4.9 所示。这里还避免了针对每种更新操作逐一定制消息。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// 代码清单 4.9 使用 std::packaged_task 在 GUI 的线程上运行代码\n",
    "\n",
    "#include <deque>\n",
    "#include <future>\n",
    "#include <iostream>\n",
    "#include <mutex>\n",
    "#include <thread>\n",
    "#include <utility>\n",
    "#include <chrono>\n",
    "#include <atomic>\n",
    "\n",
    "// 全局变量\n",
    "std::mutex m;\n",
    "std::deque<std::packaged_task<void()>> tasks;\n",
    "\n",
    "std::atomic<bool> gui_shutdown(false); // 用于控制 GUI 线程的退出\n",
    "\n",
    "// 模拟接收 GUI 关闭消息\n",
    "bool gui_shutdown_message_received() {\n",
    "    return gui_shutdown.load();\n",
    "}\n",
    "\n",
    "// 模拟处理 GUI 消息\n",
    "void get_and_process_gui_message() {\n",
    "    std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟消息处理耗时\n",
    "    std::cout << \"Processing GUI message...\" << std::endl;\n",
    "}\n",
    "\n",
    "// GUI 线程\n",
    "void gui_thread() { // 1\n",
    "    while (!gui_shutdown_message_received()) { // 2\n",
    "        get_and_process_gui_message(); // 3\n",
    "        std::packaged_task<void()> task;\n",
    "        {\n",
    "            std::lock_guard<std::mutex> lk(m);\n",
    "            if (tasks.empty()) continue; // 4\n",
    "            task = std::move(tasks.front()); // 5\n",
    "            tasks.pop_front();\n",
    "        }\n",
    "        task(); // 6\n",
    "    }\n",
    "    std::cout << \"GUI thread shutting down...\" << std::endl;\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这段代码简明易懂，在 GUI 线程上①，轮询任务队列和待处理的界面消息（如用户的单击）③；若有消息指示界面关闭，则循环终止②。假如任务队列一无所有，则循环继续④；否则，我们就从中取出任务⑤，释放任务队列上的锁，随即运行该任务⑥。在任务完成时，与它关联的 `future` 会进入就绪状态。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// 启动 GUI 线程\n",
    "std::thread gui_bg_thread(gui_thread);\n",
    "\n",
    "// 向 GUI 线程投递任务\n",
    "template <typename Func>\n",
    "std::future<void> post_task_for_gui_thread(Func f) {\n",
    "    std::packaged_task<void()> task(f); // 7\n",
    "    std::future<void> res = task.get_future(); // 8\n",
    "    {\n",
    "        std::lock_guard<std::mutex> lk(m);\n",
    "        tasks.push_back(std::move(task)); // 9\n",
    "    }\n",
    "    return res; // 10\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "向任务队列布置任务也很简单。\n",
    "\n",
    "我们依据给定的函数创建新任务，将任务包装在内⑦，并随即通过调用成员函数 `get_future()`，取得与该任务关联的 `future`⑧，然后将任务放入任务队列⑨，接着向 `post_task_for_gui_thread()`的调用者返回 `future`⑩。接下来，有关代码向 GUI 线程投递消息，假如这些代码需判断任务是否完成，以获取结果进而采取后续操作，那么只要等待 `future` 就绪即可；否则，任务的结果不会派上用场，关联的 `future`可被丢弃。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "本例采用 `std::packaged_task<void()>`表示任务，包装某个函数（或可调用对象），它不接收参数，返回 `void`（倘若真正的任务函数返回任何其他类型的值，则会被丢弃）。\n",
    "\n",
    "这里，我们采用最简单的任务举例，但前文已提过，`std::packaged_task` 也能用于更复杂的情况。针对不同的任务函数，`std::packaged_task` 的函数调用操作符须就此修改参数，保存于相关的 `future` 实例内的返回值类型也须变动，而我们只要通过模板参数，指定对应任务的函数签名即可。我们可轻松扩展上例，改动那些只准许在 GUI 线程上运行的任务，令其接收参数，并凭借 `std::future` 返回结果，`std::future` 不再局限于充当指标，示意任务是否完成。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "有些任务无法以简单的函数调用表达出来，还有一些任务的执行结果可能来自多个部分的代码。\n",
    "\n",
    "如何处理？这种情况就需运用第三种方法（第一种是 `std::async()`，第二种是 `std::package_task<>`）创建 `future`：借助 `std::promise` 显式地异步求值。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 创建 `std::promise` "
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "C++17",
   "language": "C++17",
   "name": "xcpp17"
  },
  "language_info": {
   "codemirror_mode": "text/x-c++src",
   "file_extension": ".cpp",
   "mimetype": "text/x-c++src",
   "name": "C++17",
   "version": "17"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
